By the time you read this post, my website should now be built using a new backend, MetalSmith written in Javascript.
Did you know what this website was first written using Ruby using Middleman? I was just starting to learn web design when I created the website. Having now been 5 years in the fast-paced tech industry, my tastes and preferences have changed.
It's been almost 1.5 years since my last post and I hope with the recent additons to the website, I can focus more on the synthesis of blogs. Why not kick start the month with a rewrite of the website's static site generator? I hope to highlight a few fun technological things as well as factors for my I chose the tools that I did.
Looking at my commits, this website has come a long way, here is some timeline:
At this stage, the website was a glorified about me
page with a small blog section. Raw HTML posts with Middleman were my method of posting, writing was a chore as I had to write every new break line.
Technically the backend was written with .html.erb
files which were server-based HTML files that are compiled. Similar to PHP side-html files.
Why Middleman?
index.html
.sass
support which isa more-powerful css
variant at the time.I remember these features as being revolutionary at the time but things have changed a lot since then, especially with libraries from sass
now that css
supports more features.
Libraries like bourbon
was revolutionary at the time allowing for creation of grid-boxes making dynamic websites for mobile easier.
But these tools have largely been uneeded now with modern css
.
I barely knew Ruby and had some projects in PHP + HTML/CSS. HTML5 was still a stranger to me and the idea of NodeJS and Javascript was still foreign to me. I knew I wanted a portfolio website but didn't know what to make of it.
Little did I know most of my skills I learn here would help protoype my thesis project.
I had posted a blog update to kick start this site.
By this stage, I had added a couple of plugins of Disqus and then a few Ruby helpers. I knew that I wanted to add more features and abilities and so started adding additional helpers so that I can modify HTML constructs as need-be.
Here are a few helpers I would define under config.rb
for my Middleman
instance:
# For use on header generations so that line headers were more apparent.
def ast(title_name)
return '<h2 class="ast">' + title_name + '</h2>'
end
# Article subsection title
def asst(title_name)
return '<h3 class="asst">' + title_name + '</h3>'
end
# Defining helpers for easy of rendering HTML using Ruby `.erb` files.
def pimage(src, params={}, &block)
defaults = {alt: "", link: "", width: "100%"}
params = defaults.update(params)
final_str = image_tag(src, alt: params[:alt], width: params[:width])
unless params[:link].empty?
final_str = '<a href="' + params[:link] + '" class="image-link">' + final_str + '</a>'
end
unless params[:alt].empty?
final_str += '<br /><i class="image-subtitle"> image: ' + params[:alt] + '</i>'
end
if block_given?
custom_append = block.call final_str
return CGI.unescapeHTML('<div class="image-unit">' + custom_append + '</div>')
end
return CGI.unescapeHTML('<div class="image-unit">' + final_str + '</div>')
end
Ruby allowed these helpers during the processing phase and added additional flexibility, here is one from the Lunar=Nox post:
<%= pimage "pblog/2021/equinox_thumbnail.png", link: "https://programmingincluded.itch.io/lunarequinox" do
|img_html|
+ img_html \
+ '<br /> <i class="image-subtitle"> image: Game Jam 2020 Submission! ' \
+ 'You can find the game' \
+ '<a href="https://programmingincluded.itch.io/lunarequinox"> here </a></i>'
end %>
This change allowed me not only to render the image with custom link, but also to inject a custom subtitle with link support. With Ruby, the ability to create helpers and the wrap it in a compile-side language was powerful and useful in edge-cases.
Ruby showed its strength with its strong out-of-the-box support of plugins such as Disqus but also allowed for powerful helper constructs.
I wouldn't recommend doing this now as I think this greatly complicates the render pipeline, however when everything was compile-side *.html.erb
it was fairly easy to implement.
Around 2019, after my work with a Javascript Electron
based project for my paper, Improv. My CSS and Javascript skills grew significantly and I took the time
to flush out the blog index page tapping more into the Ruby ecosystem.
The index page we see today with the cards and category render on the top-right hand side was more-or-less the same since then.
Around this time, the Ruby version I used was becoming hard to procure and install on Windows. So I updated my Ruby version and upgraded Middleman. This broke a few of my plugins and forced me fork a few plugins. Vital plugins such as the one used to create my related articles section began breaking as some of them were tied to custom Windows build binaries that were no longer supported.
At this point they were minor inconveniences, perhaps standard to software maintenance. The idea of a rewrite began to brew in my mind however.
A time-skip happened as I started working at Nvidia. The first few years were quite busy so I only focused on writing a couple of articles. But in the process that year, I decided to add a dynamic side-bar after noticing them appear throughout the tech landscape.
Around this time, I also supported markdown
rendering on the render pipeline. So my files became .html.md.erb
.
The sidebar was entirely written with JQuery
and otherwise vanilla Javascript. I am sure there are probably libraries out there now but this was written partly as a way for me to learn.
I needed a client-side way to render TOC and at the time my backend was in Ruby still so it was not immediately clear to me the way to go.
It was around this time I ran into lots of build troubles that had accumulated over the years:
build.py
was introduced so that I would work around the problem by skipping broken checksum files and then injecting them to the payload.I started to consider a rewrite in the world of NodeJs
and came across Metalsmith
.
This rewrite would start in 2023, but I wouldn't come back to it until 2025... as I started becoming busy once more.
In 2023, I had chosen Metalsmith
and had started a feat-v2
branch with a basic render pipeline setup. But it wasn't a couple days ago did I start seriously
looking into the ecosystem and hooking up the framework. It took about 16 hours of continuous porting between 2 years ago me and now to finally finish the port.
But what led me to choose Metalsmith
and why NodeJS
? I want to dedicate the rest of the article on this topic as well as highlight a few neat features.
After almost 8 years of maintaining the website, I've developed a list of things I wanted to support or have learned.
For me NodeJS
became my go-to web development language. Over the years I had worked with meteor for my thesis project, browserify for the frontend, as well as Angular.
With recent experience with vite.js it only solidified my preference in NodeJS
. These are just the NodeJs
frameworks, I had also learned Laravel
pre-2020 days. All these various frameworks made me realize the surprising amount of options available,
each with slightly different ways of doing things.
At somepoint, I had toyed with writing my own backend framework with Handlebars
and webpack
though I had forgotten what I was using it for. Regardless, these skills proved useful as almost every framework can be reduced down to a few components:
If you have these few components set, it makes things a lot easier to maintain. What NodeJs
provided were options along each of these pillars in making a good web language.
To me, NodeJs meant the ability to customize every step of the web pipeline.
Ruby (more specifically Ruby on Rails) felt revolutionary at the time, introduce Gem.lock
files and template rendering when compared to alternatives such as PHP
at the time. However, much of what ROR
offerred could be found in NodeJs
ecosystem.
ROR
in comparison felt much more rigid albeit faster in prototyping.
I had considered Rust as a potential but the ecosystem still feels too young. NodeJs
seems to me equally if not more flexible ecosystem compared to ROR
.
As a result, I ended up looking into the NodeJs
ecosystem for a good static site generator.
I needed a framework that wasn't too rigid. There were some framework where it only accepted Markdown
and generated a very standard site. However, I wanted something more customizable and something I can dissect in case I ever I need
to in house a plugin for easy maintenance.
At its core, Metalsmith
is about extensbility and is not very vocal about how things should be structured. Instead it provides concepts of out the box:
A pipeline composes of plugins and transformations of a virtual file system represented by a single dictionary.
Each file from a folder is given an object with content and metadata encoded. Each plugin
introduced would transform and operate on this list of files:
let files = {
"path_to_file.html": {metadata_1: "", content: ""},
"folder/naother.html": {metadata_1: "", content: ""}
}
// Modifying the render pipeline is as simple as modifying the dictionary and path:
files["new_file.html"] = {...}
The pipeline for Metalsmith
effectively becomes a chain of functions each mutating the dicionary.
let plugin = (files, metalsmith) => {
// mutate `files` or read meta-metadata from `metalsmith`.
}
Here is what a basic config file looks like:
// Read markdown files from source recursively and output html files in build folder.
let ms = Metalsmith("working_directory")
.source('./source')
.destination('./build')
.use(inPlace({transform: "markdown-it"}))
.build()
Any plugin becomes a file signature of (files, metalsmith)
which makes it easy to onboard.
As of the time of this writing, most features supported out-of-the-box by many static site-generators are instead packaged as plugins. Furthermore, many of these plugins use NodeJS
libraries under-the-hood.
Examples like:
metalsmith/collections
which groups files, sorts them, and indexes them then exposes them as metadata.metalsmith/layouts
uses tried and true handlebars.js to render re-usable side-wide templates.These are plugins maintained by the official devs which makes it easier to onboard too.
Since 2017, Markdown
has gained significantly popularity both in documentation as well as blog-like frameworks. Having made the switch to Markdown
in Middleman
at somepoint,
I knew at that point that writing in Markdown
was the way to go for me, specifically Markdown
with custom HTML support. That way I can focus on the content and media generation rather
than wrap my head around how the HTML will render.
With Metalsmith
this was achievable with a simple plugin: metalsmith-inplace
which renders your documents in-place using jstransformer
libraries.
For my AI friends, jstransformer
is not about transformers in neural network architecture but rather it is a library for standardizing well known language compilers in Javascript into a single interface.
import inPlace from '@metalsmith/in-place'
// It is as easy as three lines of code!
Metalsmith("working_dir")
.use(inPlace({transform: "markdown-it", engineOptions: {html: true}}))
.build()
To make porting easy and also powerful, I knew I needed something that would support templating. As mentioned above, metalsmith/layouts
would support what I needed.
Most of my code was not compatible with css
however and I needed a sass
backend, thankfully there was metalsmith/sass
package.
This was what sealed the deal for me, the sass
support as I needed a way to port easily. The sass
work was done around 2023, it took sometime to remove outdated dependencies and move most into pure sass
and flex-grid
implementations.
Another familiar addition was handlebars.js
support. Now my files can be rendered using the extension .html.md.hbs
which correlates well with my old workflow.
Metalsmith
did have its limitations. When I first looked into Metalsmith
, the project was about 2.5 years old, with the first official build in 2020. Coming into 2025, the project is now 5 years old, however it is still well maintained.
Some 3rd party plugins, however, saw little action and some of them consolidated to large repos.
But even then, the repos had little or few issues. Perhaps it is because the framework wasn't as popular, the plugins not as well used, or just well written?
The core plugins and core project do not have this issue however. Furthermore, most of the plugins I used worked well probably thanks to how the project incentivizes small plugins.
That being said, some were hard to wrangle and utilize. Certain plugins I had hoped to rely-upon did not work well with my setup such as metalsmith-collections-related
which had trouble in Windows as it requires
a C++ build internally probably for the tokenization of articles.
I ended up folding my own pipeline for recommenders using a simple tag-based system for now. That's for another article!
Another one was the long onboarding. When multiple plugins are involved, order of registration of plugins become important.
I had an issue with how my collections
were rendering URLs because I had modified my URLs using a custom pipeline render to parse my specialized naming scheme.
But I couldn't just wait until after my custom rename plugin because of how my in-place and layout renders dependended on the collection's metadata.
I ended up having to write my own work-around post-collections pipeline to fix the URL after reading the code on how the collections
plugin stored its metadata.
Now it may seem a bit hard, but to me, because of how transparent and short the plugins are, it did not take me more than an hour to implement a solution.
To get to where I am today, I read through the plugin writing doc in its entirety to fully grasp how Metalsmith
worked. After that reading, everything fit like a puzzle piece.
I eneded up writing a pipeline to support how the website was generated from Middleman
:
Handlebars.js
Handlebars.js
using handlebars-helpers
As it stands, most of the features of my old workflow has been ported over except for Disqus
which I decided to deprecate given the lack of usage. I can render just like before using helpers
:
## Markdown Works!
{{pimage "custom_image" alt-"custom text"}}
All my workflow now exists in Markdown and added html supported if need-be.
Using handlebars.js
I can register almost any function if not already supported by handlebars-helpers
which expedite the flow.
I hope this workflow will last me a few years until some dependency chain breaks and I am forced to dockerize yet again. But hopefully by then, Rust will grow mature enough for me to use it as a static website generator.
I am already familiar with Zola
however found it too rigid for my tastes.
Perhaps Rust will eventually provide plenty of tools to mix and match my webstack just like NodeJs
one day?
Perhaps WebAssembly will replace Javascript? Perhaps.